<template>
	<view class="zq-upload">
		<block v-for="(item,index) in data.files" :key="item[1].path">
			<view class="zq-upload-item" :style="{
					'width':data.itemWidth + 'rpx',
					'height':props.columnCount === 1 ? '100%' : data.itemWidth + 'rpx',
					'marginTop':(index >= props.columnCount ? props.gutter : 0) + 'rpx',
					'marginLeft': (index % props.columnCount === 0 ? 0 : props.gutter) + 'rpx'}" @click="preview(item[1])">
				<slot name="item" :item="item[1]" :index="index">
					<image :src="item[1].path" :mode="props.imageMode" class="zq-upload-item-image"
						v-if="item[1].type === 'image'"></image>
					<view class="zq-upload-item-video" v-if="item[1].type === 'video'">
						<video :src="item[1].path"></video>
					</view>
				</slot>
				<block v-if="!props.disabled">
					<slot name="bar" :item="item[1]" :index="index" v-if="!props.isProfile && props.showBottom">
						<view class="zq-upload-item-btns">
							<slot name="progress" :item="item[1]" :index="index" v-if="item[1].status === 'upload'">
								<view class="zq-upload-progress">
									<view class="zq-upload-progress-bar" :style="{'width': item[1].progress + '%'}">
									</view>
								</view>
							</slot>
							<view class="zq-upload-item-prompt"
								v-if="item[1].status === 'fail' || item[1].status === 'oversized'">
								{{item[1].status === 'fail' ? "上传失败" : "文件已超过限制大小"}}
							</view>
							<!-- <image src="./icon/edit.png" class="zq-upload-item-btn" @click.stop="edit(item[1])" v-if="item[1].status === 'success'"></image>
							<image src="./icon/preview.png" class="zq-upload-item-btn" @click.stop="preview(item[1])" v-if="item[1].status === 'success' && item[1].type === 'image'"></image> -->
							<image src="./icon/remove.png" class="zq-upload-item-btn" @click.stop="remove(item[1])"
								v-if="props.showRemove"></image>
						</view>
					</slot>
				</block>
			</view>
		</block>
		<!-- 新增 -->
		<view class="zq-upload-item" :style="{
				'width':data.itemWidth + 'rpx',
				'height':props.columnCount === 1 ? '100%' : data.itemWidth + 'rpx',
				'marginTop':(data.files.size >= props.columnCount ? props.gutter : 0) + 'rpx',
				'marginLeft': (data.files.size % props.columnCount === 0 ? 0 : props.gutter) + 'rpx'
			}" @click="add(1)"
			v-if="data.itemWidth && data.imageCount < (!props.imageCount ? (props.count - data.videoCount) : props.imageCount) && hasTypeCount(['image']) === 1 &&!props.isProfile">
			<slot name="addImage" :width="data.itemWidth">
				<view class="zq-upload-add" :style="{
						'width':data.itemWidth + 'rpx',
						'height':props.columnCount === 1 ? '100%' : data.itemWidth + 'rpx'}">
					<view>+</view>
					<view>{{addImageText}}</view>
				</view>
			</slot>
		</view>
		<view class="zq-upload-item" :style="{
				'width':data.itemWidth + 'rpx',
				'height':props.columnCount === 1 ? '100%' : data.itemWidth + 'rpx',
				'marginTop':((data.files.size + hasTypeCount(['image'])) >= props.columnCount ? props.gutter : 0) + 'rpx',
				'marginLeft': ((data.files.size + hasTypeCount(['image'])) % props.columnCount === 0 ? 0 : props.gutter) + 'rpx'
			}" @click="add(2)"
			v-if="data.itemWidth && data.videoCount < (!props.videoCount ? (props.count - data.imageCount) : props.videoCount) && hasTypeCount(['video']) === 1 &&!props.isProfile">
			<slot name="addVideo" :width="data.itemWidth">
				<view class="zq-upload-add" :style="{
					'width':data.itemWidth + 'rpx',
					'height':props.columnCount === 1 ? '100%' : data.itemWidth + 'rpx'}">
					<view>+</view>
					<view>{{addVideoText}}</view>
				</view>
			</slot>
		</view>
		<view class="zq-upload-profile"
			:style="{'width':data.itemWidth + 'rpx','height':props.columnCount === 1 ? '100%' : data.itemWidth + 'rpx'}"
			@click="add(3)" v-if="data.itemWidth && props.isProfile">
			<slot name="add" :width="data.itemWidth">
				<view class="zq-upload-profile-add">
					<image src="@/static/images/index/camera.png"></image>
				</view>
			</slot>
		</view>
	</view>
	<!-- 提示 -->
	<view class="zq-upload-model-bg" v-if="data.showRemoveModel">
		<view class="zq-upload-model">
			<view class="zq-upload-model-text">
				文件{{data.file.status === "success" ? "已经上传成功" : "正在上传中"}}，确定将该文件删除吗？删除后无法恢复</view>
			<view class="zq-upload-model-btns">
				<view @click="confirmRemove()">确定</view>
				<view @click="closeRemove()">取消</view>
			</view>
		</view>
	</view>

	<!-- <video
		id="coverVideo"
		ref="coverVideo"
		class="zq-upload-cover-video"
		:style="{
			'width': data.coverVideoWidth + 'px',
			'height': data.coverVideoHeight + 'px',
		}"
		muted
		></video> -->
	<!-- :controls="false"
		:show-center-play-btn="false" -->
	<!-- <canvas
		class="zq-upload-cover-canvas"
		id="coverCanvas"
		canvas-id="coverCanvas"
		:style="{
			'width': data.coverVideoWidth + 'px',
			'height': data.coverVideoHeight + 'px',
		}"></canvas> -->
</template>

<script lang="ts" setup>
	import { reactive, watch, onMounted, getCurrentInstance, nextTick } from "vue";

	interface file {
		id ?: number,
		type : string,//文件类型 image/video/file
		path : string,
		url ?: string,
		key ?: string,
		status ?: string,
		size ?: number,
		progress ?: number
		width ?: number,
		height ?: number,
		cover ?: string,//封面
		duration ?: number,//时长
		time ?: number//当前时长
	}

	const { proxy } = getCurrentInstance();
	const props = withDefaults(defineProps<{
		files : file[],//文件列表
		action ?: string,//请求接口
		imageAction ?: string,//图片请求接口
		videoAction ?: string,//视频请求接口
		fileType ?: string[],//文件类型 image/video/file
		name ?: string,//文件对应的 key , 开发者在服务器端通过这个 key 可以获取到文件二进制内容
		header ?: any,//请求头
		formData ?: any,//请求参数
		imageFormData ?: any,//图片请求参数
		videoFormData ?: any,//视频请求参数
		compressed ?: boolean,//视频是否压缩
		sizeType ?: string[],//original 原图，compressed 压缩图，默认二者都有
		sourceType ?: ("album" | "camera")[],//album 从相册选图，camera 使用相机，默认二者都有
		timeout ?: number,//超时时间
		asyn ?: boolean,//是否异步
		uploaded : Function,//请求成功处理函数
		preview ?: Function | null,//预览函数
		columnCount ?: number,//每列个数
		gutter ?: number,//每列间隔
		addImageText ?: string,//添加按钮提示语
		addVideoText ?: string,//添加按钮提示语
		imageMode ?: string,//图片展示方式
		maxSize ?: number,//图片最大内存
		maxDuration ?: number,//视频长度
		count ?: number,//文件数量
		imageCount ?: number | null,//图片数量
		videoCount ?: number | null,//视频数量
		isProfile ?: boolean,//是否人像
		showBottom ?: boolean,//显示所有按钮
		showRemove ?: boolean//显示删除按钮
		disabled ?: boolean//禁用
		removeModel ?: boolean//是否删除时提示
	}>(), {
		files: () => [],
		action: "",
		imageAction: "",
		videoAction: "",
		fileType: () => ["image"],
		name: 'file',
		header: () => { },
		formData: () => { },
		imageFormData: null,//图片请求参数
		videoFormData: null,//视频请求参数
		timeout: 60000,
		compressed: true,
		sizeType: () => ["original", "compressed"],
		sourceType: () => ["album", "camera"],
		asyn: false,
		uploaded: (file : file, response : any) : file => { return file },
		preview: null,
		columnCount: 1,
		gutter: 20,
		addImageText: "上传图片",
		addVideoText: "上传视频",
		imageMode: "aspectFit",
		maxSize: -1,
		maxDuration: 60,
		count: 1,
		imageCount: null,//图片数量
		videoCount: null,//视频数量
		isProfile: false,
		showBottom: true,
		showRemove: true,
		disabled: false,
		removeModel: true
	})
	const emits = defineEmits<{
		(e : "update:files", files : file[]) : void,
		(e : "success", files : file[], file : file) : void,
		(e : "remove", files : file[], file : file) : void,
		(e : "error", files : file[], file : file) : void,
		(e : "change", files : file[]) : void
	}>()

	const data = reactive({
		itemWidth: 0,
		count: 1,
		files: new Map(),
		imageCount: 0,//图片数量
		videoCount: 0,//视频数量
		file: null,
		showRemoveModel: false,
		coverVideoSrc: "",
		coverVideoWidth: 100,
		coverVideoHeight: 100,
		coverCanvas: null,
		coverVideo: null
	})

	const add = (type : number) : void => {//选择图片
		if (props.disabled) return
		if (type === 1) {
			chooseImages();
		} else if (type === 2) {
			chooseVideo();
		} else if (type === 3) {
			let oldFile = undefined;
			data.files.forEach((file : file) : void => {
				oldFile = file;
			})
			if (oldFile && oldFile.status === "upload") return
			if (hasTypeCount(["image", "video"]) === 2) {
				selectMedia(oldFile);
			} else if (hasTypeCount(["image"]) === 1) {
				chooseImages(oldFile);
			} else if (hasTypeCount(["video"]) === 1) {
				chooseVideo(oldFile);
			}
		}
	}
	const edit = (item : file) : void => {//修改文件
		if (props.disabled) return
		if (item.type === "image") {
			chooseImages(item);
		} else if (item.type === "video") {
			chooseVideo(item);
		}
	}
	const chooseImages = (file ?: file) : void => {//选择照片
		if (file === undefined && ((props.imageCount || props.count) <= data.imageCount)) return
		uni.chooseImage({
			count: props.imageCount || props.count,
			sizeType: props.sizeType,
			sourceType: props.sourceType,
			success: (event : any) => {
				event.tempFiles.map((obj : any) => {
					uni.getImageInfo({
						src: obj.path,
						success: (info) => {
							obj.key = obj.path;
							obj.size = obj.size;
							obj.width = info.width;
							obj.height = info.height;
							obj.type = "image";
							obj.status = "start";
							obj.url = "";
							uploadBefore(obj, file);
						}
					})
				})
			}
		})
	}
	const chooseVideo = (file ?: file) : void => {//选择视频
		if (file === undefined && ((props.videoCount || props.count) <= data.videoCount)) return
		uni.chooseVideo({
			sourceType: props.sourceType,
			camera: "back",
			maxDuration: props.maxDuration,
			compressed: props.compressed,
			success: (event) => {
				console.log("event", event)
				uploadBefore({
					path: event.tempFilePath,
					key: event.tempFilePath,
					size: event.size,
					width: event.width,
					height: event.height,
					duration: event.duration,
					time: 0,
					type: "video",
					status: "start",
					url: "",
				}, file)
			}
		})
	}
	const selectMedia = (file ?: file) : void => {//选择多媒体
		uni.chooseMedia({
			count: 9,
			mediaType: ['image', 'video'],
			sourceType: ['album', 'camera'],
			maxDuration: 30,
			camera: 'back',
			success(res) {
				console.log("res", res);
				res.tempFiles.map((obj : any) => {
					uploadBefore({
						path: obj.tempFilePath,
						key: obj.tempFilePath,
						size: obj.size,
						width: obj.width,
						height: obj.height,
						duration: obj.duration || 0,
						cover: obj.thumbTempFilePath || "",
						time: 0,
						type: res.type,
						status: "start",
						url: "",
					}, file)
				})

			}
		})
	}
	const uploadBefore = (file : file, item ?: file) : any => {//上传前
		if (item === undefined) {
			if (file.type === "image") {
				data.imageCount++;
			} else if (file.type === "video") {
				data.videoCount++;
			}
		}
		if (props.maxSize > 0 && file.size > props.maxSize && file.type === "image") {
			file.status = "oversized";
		}
		file.progress = 0;
		if (item !== undefined) file.key = item.key;
		data.files.set(file.key, file);
		emits("update:files", serialize());
		if (file.status === "start") {
			upload(data.files.get(file.key));
		}
	}
	const upload = (file : file) : void => {//上传文件
		file.status = "upload";
		console.log("data", data)
		console.log("props", props)
		console.log("file", file)
		const uploadTask = uni.uploadFile({
			url: (file.type === "image" ? props.imageAction : file.type === "video" ? props.videoAction : null) || props.action,
			filePath: file.path,
			name: props.name,
			formData: (file.type === "image" ? props.imageFormData : file.type === "video" ? props.videoFormData : null) || props.formData,
			header: props.header,
			success: (uploadFileRes) => {
				console.log("uploadFileRes", JSON.parse(uploadFileRes.data))
				file = props.uploaded(file, JSON.parse(uploadFileRes.data));
				console.log("file", file)
				// if(file.type === "video"){
				// 	getVideoCover(file)
				// }
				if (file.status === "success") {
					emits("success", serialize(), file);
				}
				if (file.status === "fail") {
					emits("error", serialize(), file);
				}
				emits("change", serialize().filter(obj => obj.status === "success"));
				emits("update:files", serialize());
			},
			fail: (res) => {
				console.log("失败了", res);
				file.status = "fail";
				emits("error", serialize(), file);
				emits("update:files", serialize());
			}
		});
		uploadTask.onProgressUpdate((res) => {
			if (!data.files.has(file.key)) {
				uploadTask.abort();
				return;
			}
			file.progress = res.progress;
			emits("update:files", serialize());
		});
	}
	const remove = (file : file) : void => {//删除
		if (props.disabled) return
		if (file.status === "fail" || file.status === "oversized") {
			data.files.delete(file.key);
			if (file.type === "image") data.imageCount -= 1;
			if (file.type === "video") data.videoCount -= 1;
			emits("remove", serialize(), file);
			emits("change", serialize().filter(obj => obj.status === "success"));
			emits("update:files", serialize());
		} else {
			data.file = file;
			if (props.removeModel) {
				data.showRemoveModel = true;
			} else {
				confirmRemove()
			}
		}
	}
	const confirmRemove = () : void => {//确定删除
		data.showRemoveModel = false;
		if (data.file.type === "image") data.imageCount -= 1;
		if (data.file.type === "video") data.videoCount -= 1;
		data.files.delete(data.file.key);
		emits("remove", serialize(), data.file);
		emits("change", serialize().filter(obj => obj.status === "success"));
		emits("update:files", serialize());
		data.file = null;
	}
	const closeRemove = () : void => {//取消删除
		data.showRemoveModel = false;
		data.file = null;
	}
	const preview = (item : file) : any => {//查看
		if (props.disabled) return
		if (!props.preview && item.type === "image") {
			uni.previewImage({ urls: [item.path] })
		} else if (!props.preview && item.type === "video") {

		}
	}
	const serialize = () : file[] => {//系列化
		const files : file[] = [];
		data.files.forEach((file : file) : void => {
			files.push(file);
		})
		return files;
	}
	const hasTypeCount = (keys : string[]) : number => {//包含类型数量
		let count : number = 0;
		keys.forEach(key => {
			if (props.fileType.findIndex(item => item === key) > -1) count++
		})
		return count;
	}
	const getVideoCover = (file : file) : void => {
		data.coverVideoWidth = file.width
		data.coverVideoHeight = file.height
		data.coverVideoSrc = file.path
		if (!data.coverVideo) {
			data.coverVideo = uni.createVideoContext("coverVideo", proxy)
			data.coverVideo.controls = true
			data.coverVideo.autoplay = false
			data.coverVideo.loop = true
		}
		if (!data.coverCanvas) {
			data.coverCanvas = uni.createCanvasContext("coverCanvas", proxy)
		}
		data.coverVideo.src = file.url
		console.log(data.coverVideo)
		data.coverVideo.play()

		setTimeout(() => {
			data.coverCanvas.drawImage(data.coverVideo, 0, 0, file.width, file.height)
			console.log(data.coverCanvas)
			data.coverCanvas.draw()
		}, 1000)
	}
	const wacthFiles = (files : file[]) : void => {//初始化
		if (files.length > 0) {
			files.forEach((obj : any) : void => {
				if (obj.path) {
					if (!obj.status) obj.status = "start";
					if (!obj.key) obj.key = obj.url || obj.path;
					if (!obj.path) obj.path = obj.url;
					if (!obj.type) obj.type = "image";
					if (obj.status === "success" && !obj.url) obj.url = obj.path;
					if (obj.status === "start") {
						uploadBefore(obj);
					} else {
						if (!data.files.has(obj.key)) {
							data.files.set(obj.key, obj);
							if (obj.type === "image") {
								data.imageCount += 1;
							} else if (obj.type === "video") {
								data.videoCount += 1;
							}
						}
					}
				}
			})
		}
	}

	watch(() => [...props.files], (nVal : any) : void => {
		wacthFiles(nVal);
	}, { deep: true })
	onMounted(() => {
		nextTick(() => {
			setItemWidth();
			wacthFiles(props.files);
		})
	})

	let initItemWidthCount = 0;
	const setItemWidth = () : void => {//获取初始数据
		const views = uni.createSelectorQuery().in(proxy);
		views.selectAll('.zq-upload').boundingClientRect((info : any) => {
			if (!info || info.length === 0) {
				setTimeout(setItemWidth, 50)
				return
			}
			const { width } = info[0];
			if (width === 0) {
				initItemWidthCount++;
				if (initItemWidthCount > 50) return
				setTimeout(setItemWidth, 50)
				return
			}
			uni.getSystemInfo({
				success: (event) => {
					data.itemWidth = Math.floor((width * 750 / event.screenWidth - (props.columnCount - 1) * props.gutter) / props.columnCount);
				}
			})
		}).exec();
	}
</script>

<style lang="scss">
	.zq-upload {
		position: relative;
		display: flex;
		flex-direction: row;
		flex-wrap: wrap;
		width: 100%;
		height: 100%;

		&.zq-upload-profile {
			border-radius: 50%;
			overflow: hidden;
		}

		.zq-upload-item {
			position: relative;

			.zq-upload-item-image {
				width: 100%;
				height: 100%;
				background: #f2f2f2;
			}

			.zq-upload-item-video {
				width: 100%;
				height: 100%;
				position: relative;
				background: #f2f2f2;

				video {
					width: 100%;
					height: 100%;
				}
			}

			.zq-upload-item-btns {
				position: absolute;
				bottom: 0;
				left: 0;
				width: 100%;
				height: 60rpx;
				background: rgba(0, 0, 0, 0.1);
				display: flex;
				flex-direction: row;
				justify-content: center;
				align-items: center;

				.zq-upload-progress {
					width: 80%;
					height: 16rpx;
					background: rgba(0, 0, 0, 0.2);
					border-radius: 8rpx;
					overflow: hidden;

					.zq-upload-progress-bar {
						width: 300rpx;
						height: 16rpx;
						background: #15BB82;
						border-radius: 8rpx;
						transition: all 0.3s;
					}
				}

				.zq-upload-item-prompt {
					font-size: 24rpx;
					color: #F63333;
					text-align: center;
				}

				.zq-upload-item-btn {
					width: 42rpx;
					height: 42rpx;
					margin: 0 10rpx;
					transition: all 0.3s;

					&:active {
						transform: scale(0.9);
					}
				}
			}

			.zq-upload-add {
				display: flex;
				flex-direction: column;
				align-items: center;
				justify-content: center;
				width: 100%;
				height: 100%;
				font-size: 22rpx;
				color: #999999;
				background: #f2f2f2;

				view {
					&:nth-child(1) {
						font-size: 40rpx;
						font-weight: bold;
					}
				}

				.zq-upload-item-add-profile {
					width: 120rpx;
					height: 120rpx;
				}
			}
		}

		.zq-upload-profile {
			position: relative;

			.zq-upload-profile-add {
				display: flex;
				flex-direction: column;
				align-items: center;
				justify-content: center;
				width: 100%;
				height: 100%;
				background: rgba(0, 0, 0, 0.2);
				border-radius: 50%;
				overflow: hidden;

				image {
					width: 80rpx;
					height: 80rpx;
				}
			}
		}
	}

	.zq-upload-model-bg {
		position: fixed;
		z-index: 998;
		left: 0;
		top: 0;
		width: 100%;
		height: 100vh;
		background: rgba(0, 0, 0, 0.4);

		.zq-upload-model {
			position: absolute;
			left: 50%;
			top: 50%;
			transform: translate(-50%, -50%);
			width: 560rpx;
			background: #ffffff;
			animation: show 0.3s;

			.zq-upload-model-text {
				padding: 80rpx 60rpx 0 60rpx;
				font-size: 28rpx;
				color: #181D2D;
				text-align: center;
				line-height: 42rpx;
			}

			.zq-upload-model-btns {
				width: 100%;
				display: flex;
				flex-direction: row;
				align-items: center;
				font-size: 32rpx;
				color: #181D2D;
				text-align: center;
				line-height: 160rpx;

				view {
					transition: all 0.3s;
					width: 50%;

					&:nth-child(1) {
						color: #F63333;
					}
				}
			}
		}
	}

	.zq-upload-cover-video {
		position: fixed;
		left: 0;
		top: 0;
		background: rgba(0, 255, 0, 1);
	}

	.zq-upload-cover-canvas {
		position: fixed;
		z-index: 999;
		left: 0;
		top: 0;
		background: rgba(255, 0, 0, 0.5);
	}

	@keyframes show {
		0% {
			opacity: 0;
			transform: translate(-50%, 50%);
		}

		100% {
			opacity: 1;
			transform: translate(-50%, -50%);
		}
	}
</style>